import time
from typing import Dict, Tuple, Optional


import gurobipy as gp
from gurobipy import GRB
import numpy as np
import matplotlib.pyplot as plt
from typing import Dict, List




class SemiconductorScheduler:
    def __init__(self, params, time_limit=600):
        """初始化模型参数并创建Gurobi模型，优先使用启发式求解"""

        self.params = params  # 模型参数字典
        self.model = gp.Model("Semiconductor_Scheduler")

        # 设置求解时间限制（秒）
        self.model.setParam('TimeLimit', time_limit)
        # 调整MIPGap，允许一定的最优性差距
        self.model.setParam('MIPGap', 0.1)
        #self.model.setParam('SolutionLimit', 1)  # 找到1个可行解就停止

        # 启发式算法增强参数
        # 提高启发式算法的迭代次数（默认值为10，增大可增强启发式搜索）
        self.model.setParam('Heuristics', 0.8)  # 取值范围[0,1]，越大越优先启发式
        # 减少精确算法（分支定界）的时间占比，优先启发式
        self.model.setParam('Cuts', 0)  # 关闭切割平面
        # 增强可行性启发式（快速寻找可行解），取值范围通常为 0~10（数值越大，启发式搜索强度越高）
        self.model.setParam('RINS', 10)
        # 允许更早启动启发式算法，优先在搜索初期启动启发式
        self.model.setParam('MIPFocus', 1)  # 优先启动启发式寻找可行解
        # 限制分支定界树的规模，迫使其依赖启发式
        # self.model.setParam('NodeLimit', 1000)  # 限制节点数量，超过后依赖启发式


        print("====================== begin 初始化决策变量 =========================")
        self.initialize_variables()  # 初始化决策变量
        print("====================== end 初始化决策变量 =========================")

        print("====================== begin 设置目标函数 =========================")
        self.set_objective()
        print("====================== end 设置目标函数 =========================")

        print("====================== begin 添加约束条件 =========================")
        self.add_constraints()
        print("====================== end 添加约束条件 =========================")

    def initialize_variables(self):
        """初始化所有决策变量"""

        J = self.params['J']  # 工序集合
        K = self.params['K']  # 机器集合
        R = self.params['R']  # 机械手集合
        PM = self.params['PM']  # 加工可行矩阵
        TM = self.params['TM']  # 运输可行矩阵
        Nj = self.params['Nj']

        # 决策变量定义
        self.Cmax = self.model.addVar(name="Cmax")  # 最大完工时间

        # 工序j的实际加工/实际运输时间
        self.Pt = self.model.addVars(J, name="Pt")
        self.Tt = self.model.addVars(J, name="Tt")

        # 工序j的加工/运输开始时刻
        self.SO = self.model.addVars(J, name="SO")  # 加工开始时间
        self.ST = self.model.addVars(J, name="ST")  # 运输开始时间

        # 加工任务分配变量 (1 if 工序j在机器k上加工)
        self.phi = self.model.addVars(J, K, vtype=GRB.BINARY, name="phi")

        # 运输任务分配变量 (1 if 工序j由机械手r运输)
        self.mu = self.model.addVars(J, R, vtype=GRB.BINARY, name="mu")

        # 机器k上工序j优先于j'的排序变量
        # self.y = self.model.addVars(J, J, K, vtype=GRB.BINARY, name="y")
        self.y = {}
        for j in J:
            for j_prime in J:
                if j != j_prime:
                    for k in K:
                        # 从加工可行字典中获取j,j_prime的加工机器
                        if PM.get((j, k), 0) == 1 and PM.get((j_prime, k), 0) == 1:
                            self.y[(j, j_prime, k)] = self.model.addVar(vtype=GRB.BINARY,
                                                                        name=f"y_{j}_{j_prime}_{k}")

        # 仅对“同一工件相邻工序+可行机械手”创建z变量（450个左右）
        self.z = {}
        Nj = self.params['Nj']
        TM = self.params['TM']
        for group in Nj:  # 遍历每个工件的工序
            for i in range(len(group) - 1):
                j_prev = group[i]
                j_curr = group[i + 1]
                for r in R:
                    # 仅当两个工序都可由机械手r运输时，创建z变量
                    if TM.get((j_prev, r), 0) == 1 and TM.get((j_curr, r), 0) == 1:
                        self.z[(j_prev, j_curr, r)] = self.model.addVar(
                            vtype=GRB.BINARY,
                            name=f"z_adj_{j_prev}_{j_curr}_{r}"
                        )

        # 新增：单独存储跨工件的临时变量，不与self.z混淆
        self.temp_z = {}

        self.model.update()

    def set_objective(self):
        """设置目标函数: 最小化最大完工时间"""
        self.model.setObjective(self.Cmax, GRB.MINIMIZE)

    def add_constraints(self):
        """添加所有约束条件"""
        print("======================begin 子问题1: 加工任务分配 约束=========================")
        self.add_process_assignment_constraints()  # 子问题1: 加工任务分配

        print("======================begin 子问题2: 加工任务排序 约束=========================")
        self.add_process_scheduling_constraints()  # 子问题2: 加工任务排序

        print("======================begin 子问题3: 运输任务分配 约束=========================")
        self.add_transport_assignment_constraints()  # 子问题3: 运输任务分配

        print("======================begin 子问题4: 运输任务排序 约束=========================")
        self.add_transport_scheduling_constraints()  # 子问题4: 运输任务排序

        print("======================begin 加工-运输关联 约束=========================")
        self.add_link_constraints()  # 加工-运输关联约束

        print("======================begin 目标函数 约束=========================")
        self.add_objective_constraints()  # 目标函数约束

    def add_process_assignment_constraints(self):
        """子问题1: 加工任务分配约束"""

        J = self.params['J']
        K = self.params['K']
        PM = self.params['PM']  # 加工可行性矩阵
        PP = self.params['PP']  # 加工时间矩阵

        # 公式(3): 每个工序必须分配到唯一机器
        for j in J:
            self.model.addConstr(gp.quicksum(self.phi[j, k] for k in K) == 1,
                                 name=f"process_unique_{j}")

        # 公式(4): 加工可行性约束
        for j in J:
            for k in K:
                self.model.addConstr(self.phi[j, k] <= PM[j, k],
                                     name=f"process_feasible_{j}_{k}")

        # 公式(5): 实际加工时间计算
        for j in J:
            self.model.addConstr(self.Pt[j] == gp.quicksum(self.phi[j, k] * PP[j, k] for k in K),
                                 name=f"process_time_{j}")

    def add_process_scheduling_constraints(self):
        """子问题2: 加工任务排序约束"""

        J = self.params['J']
        K = self.params['K']
        M = self.params['M']  # 大常数
        Ns = self.params['Ns']  # 相同工艺路径、同一工艺步骤的工序集合
        Ne = self.params['Ne']  # 相同工艺路径中最后一个工序

        # 公式(6)(7): 同一机器上的加工顺序约束
        for (j,j_prime,k) in self.y:
            # 获取可以在k上加工的所有工序

            # 工序j优先于j'
            self.model.addConstr(
                self.SO[j_prime] >= self.SO[j] + self.Pt[j]
                - M * (2 - self.phi[j, k] - self.phi[j_prime, k])
                - M * (1 - self.y[j, j_prime, k]),
                name=f"process_order1_{j}_{j_prime}_{k}"
            )
            # 工序j'优先于j
            self.model.addConstr(
                self.SO[j] >= self.SO[j_prime] + self.Pt[j_prime]
                - M * (2 - self.phi[j, k] - self.phi[j_prime, k])
                - M * self.y[j, j_prime, k],
                name=f"process_order2_{j}_{j_prime}_{k}"
            )

        # 公式(8): 禁止超片约束
        # group表示相同工艺路径的晶圆、相同工艺步骤的集合
        for group in Ns:
            # 确保工序按编号从小到大排序
            sorted_group = group  # 如[1,8,15,22,...]

            # 仅遍历相邻的工序对（利用传递性减少约束数量）
            for i in range(len(sorted_group) - 1):
                j_prev = sorted_group[i]  # 前一个工序（编号小）
                j_curr = sorted_group[i + 1]  # 后一个工序（编号大）

                # 遍历所有机器
                for k in K:
                    # 约束：若j_prev和j_curr在同一机器k上加工，则j_prev完成后j_curr才能开始
                    self.model.addConstr(
                        self.SO[j_curr] >= self.SO[j_prev] + self.Pt[j_prev]
                        - M * (2 - self.phi[j_prev, k] - self.phi[j_curr, k]),
                        name=f"no_overtaking_adj_{j_prev}_{j_curr}_machine_{k}"
                    )

    def add_transport_assignment_constraints(self):
        """子问题3: 运输任务分配约束"""

        J = self.params['J']
        R = self.params['R']
        TM = self.params['TM']  # 运输可行性矩阵

        # 公式9：运输任务唯一性约束
        for j in J:
            self.model.addConstr(gp.quicksum(self.mu[j, r] for r in R) == 1,
                                 name=f"transport_unique_{j}")

        # 公式(10)：运输可行性约束
        for j in J:
            for r in R:
                self.model.addConstr(self.mu[j, r] <= TM[j, r],
                                     name=f"transport_feasible_{j}_{r}")

    def add_transport_scheduling_constraints(self):
        """子问题4: 运输任务排序约束"""

        J = self.params['J']
        R = self.params['R']
        M = self.params['M']  # 大常数
        K = self.params['K']

        TT = self.params['TT'] # 运输时间
        Ns = self.params['Ns']
        Nj = self.params['Nj']

        TM = self.params['TM']

        # 场景1：同一工件相邻工序
        for group in Nj:  # group：同一工件的工序列表（如[1-7]）
            for i in range(len(group) - 1):
                j_prev = group[i]
                j_curr = group[i + 1]
                for r in R:
                    if (j_prev, j_curr, r) in self.z:
                        # 强制j_prev先于j_curr（工艺固定顺序）
                        self.model.addConstr(
                            self.z[(j_prev, j_curr, r)] == 1,
                            name=f"z_force_{j_prev}_{j_curr}_{r}"
                        )
                        # 顺序约束：j_curr的运输开始 ≥ j_prev的运输完成
                        self.model.addConstr(
                            self.ST[j_curr] >= self.ST[j_prev] + self.Tt[j_prev]
                            - M * (2 - self.mu[j_prev, r] - self.mu[j_curr, r]),
                            name=f"trans_adj_{j_prev}_{j_curr}_{r}"
                        )

            # 场景2：跨工件任务（动态约束）
        for r in R:
            J_r = [j for j in J if TM.get((j, r), 0) == 1]
            if len(J_r) < 2:
                continue

            for i in range(len(J_r)):
                j1 = J_r[i]
                for j2 in J_r[i + 1:]:
                    # 跳过同一工件相邻工序
                    is_same_job_adj = any(
                        j1 in group and j2 in group and abs(group.index(j1) - group.index(j2)) == 1
                        for group in Nj
                    )
                    if is_same_job_adj:
                        continue

                    # 正确：存入self.temp_z，不与self.z混淆
                    self.temp_z[(j1, j2, r)] = self.model.addVar(
                        vtype=GRB.BINARY,
                        name=f"temp_z_{j1}_{j2}_{r}"
                    )
                    temp_z = self.temp_z[(j1, j2, r)]

        #
        # 公式(13)：运输禁止超片约束
        # group表示相同工艺路径的晶圆、相同工艺步骤的集合
        for group in Ns:
            # 确保工序按编号从小到大排序
            sorted_group = group  # 如[1,8,15,22,...]

            # 仅遍历相邻的工序对（利用传递性减少约束数量）
            for i in range(len(sorted_group) - 1):
                j_prev = sorted_group[i]  # 前一个工序（编号小）
                j_curr = sorted_group[i + 1]  # 后一个工序（编号大）

                # 遍历所有机器
                for k in K:
                    # 约束：若j_prev和j_curr在同一机器k上加工，则j_prev完成后j_curr才能开始
                    self.model.addConstr(
                        self.ST[j_curr] >= self.ST[j_prev] + self.Tt[j_prev],
                        name=f"no_overtaking_adj_{j_prev}_{j_curr}_machine_{k}"
                    )

    def add_link_constraints(self):
        """加工与运输关联约束（公式14-21）"""

        J = self.params['J']
        Nf = self.params['Nf']  # 首道工序集合
        alpha = self.params['alpha']  # JIT加工驻留时间（15s）
        beta = self.params['beta']    # JIT运输驻留时间（30s）
        TT = self.params['TT']        # 运输时间矩阵
        Nj = self.params['Nj']        # 每个工件的所有工序集合
        M = self.params['M']

        for j in J:
            # 首工序
            if j in Nf:
                # 首工序运输任务直接等于9秒
                self.model.addConstr(
                    self.Tt[j] == 9,
                    name=f"transport_time_exact_{j}"
                )
            else:
                # 中间工序运输任务直接等于10秒
                self.model.addConstr(
                    self.Tt[j] == 10,
                    name=f"transport_time_exact_{j}"
                )


        # 公式(18)：运输完成后才能开始加工
        for j in J:
            self.model.addConstr(
                self.SO[j] >= self.ST[j] + self.Tt[j],
                name=f"transport_before_process_{j}"
            )

        # 同一个工件的所有工序：后一个工序的运输开始实际必须大于前一个工序的加工完成时间
        for group in Nj:
            # group为同一工件的所有工序（按顺序排列）
            # 遍历相邻工序对（j_prev为前序工序，j_curr为后序工序）
            for i in range(len(group) - 1):
                j_prev = group[i]
                j_curr = group[i + 1]
                # 约束：j_curr的运输开始时间 > j_prev的加工完成时间
                self.model.addConstr(
                    self.ST[j_curr] >= self.SO[j_prev] + self.Pt[j_prev],
                    name=f"job_sequence_{j_prev}_{j_curr}"
                )

                # # 驻留时间<15
                # self.model.addConstr(
                #     self.ST[j_curr] - (self.SO[j_prev] + self.Pt[j_prev]) <= alpha,
                #     name=f"jit_process_residence_{j}"
                # )
                #
                # # 运输时间<30
                # self.model.addConstr(
                #     self.Tt[j_curr] <=  beta,
                #     name=f"jit_transport_residence_{j}"
                # )

            # # JIT驻留时间约束
            # for i in range(len(group) - 1):
            #     j_prev = group[i]
            #     j_curr = group[i + 1]
            #     # JIT加工驻留时间约束
            #     self.model.addConstr(self.ST[j_curr] <= self.SO[j_prev] + self.Pt[j_prev] + alpha,
            #          name=f"jit_process_residence_{j}"
            #     )
            #     # JIT运输驻留时间约束
            #     self.model.addConstr(self.ST[j_curr] + self.Tt[j_curr] <= self.SO[j_prev] + self.Pt[j_prev] + beta,
            #         name=f"jit_transport_residence_{j}"
            #     )



    def add_objective_constraints(self):
        """目标函数相关约束"""
        J = self.params['J']
        Ne = self.params['Ne']  # 最后一道工序集合

        # 公式(2): 最大完工时间约束
        for j in Ne:
            self.model.addConstr(self.Cmax >= self.SO[j] + self.Pt[j],
                                 name=f"objective_{j}")

    # def optimize(self):
    #     """求解模型并返回结果"""
    #
    #     # 记录开始时间
    #     print("====================开始求解模型=================")
    #     start_time = time.time()
    #     # 求解模型
    #     self.model.optimize()
    #     # 计算求解耗时
    #     solve_time = time.time() - start_time
    #
    #
    #     # 检查求解状态：同时处理最优解和可行解
    #     if self.model.status in [GRB.OPTIMAL,  GRB.SUBOPTIMAL]:
    #         '''
    #         输出：
    #         makespan
    #         每个加工任务开始时刻
    #         每个运输任务开始时刻
    #         '''
    #         result = {
    #             "solve_time": solve_time,
    #             "Cmax": self.Cmax.X,
    #             "Pt": {j: self.Pt[j].X for j in self.params['J']},
    #             "Tt": {j: self.Tt[j].X for j in self.params['J']},
    #             "SO": {j: self.SO[j].X for j in self.params['J']},
    #             "ST": {j: self.ST[j].X for j in self.params['J']},
    #             "phi": {j: [k for k in self.params['K'] if self.phi[j, k].X == 1][0] for j in self.params['J']},
    #             "mu": {j: [r for r in self.params['R'] if self.mu[j, r].X == 1][0] for j in self.params['J']},
    #             "y": {(j, jp, k): v.X for (j, jp, k), v in self.y.items() if v.X == 1},
    #             "z": {(j, jp, r): v.X for (j, jp, r), v in self.z.items() if v.X == 1},
    #             "temp_z": {(j1, j2, r): v.X for (j1, j2, r), v in self.temp_z.items() if v.X == 1}  # 新增temp_z结果
    #         }
    #         return result
    #     else:
    #         # 新增IIS分析，定位冲突约束
    #         if self.model.status == GRB.INFEASIBLE:
    #             print("模型不可行，生成冲突约束文件...")
    #             self.model.computeIIS()
    #             self.model.write("infeasible_constraints.ilp")
    #             print("冲突约束已保存到 infeasible_constraints.ilp")
    #         print(f"求解状态: {self.model.status}")
    #         return {"solve_time": solve_time, "status": self.model.status}

    def optimize(self):
        print("====================开始求解模型=================")
        start_time = time.time()
        self.model.optimize()
        solve_time = time.time() - start_time

        # 关键：检查是否存在可行解（无论状态码如何）
        has_feasible = self.model.SolCount > 0  # SolCount：可行解数量

        if has_feasible:
            try:
                result = {
                    "solve_time": solve_time,
                    "status": self.model.status,  # 保留状态码（如9表示超时）
                    "Cmax": self.Cmax.X,
                    "Pt": {j: self.Pt[j].X for j in self.params['J']},
                    "Tt": {j: self.Tt[j].X for j in self.params['J']},
                    "SO": {j: self.SO[j].X for j in self.params['J']},
                    "ST": {j: self.ST[j].X for j in self.params['J']},
                    # 安全获取 phi（避免因浮点数误差导致的空列表）
                    "phi": {},
                    "mu": {}
                }
                # 处理 phi（机器分配）
                for j in self.params['J']:
                    machines = [k for k in self.params['K'] if abs(self.phi[j, k].X - 1) < 1e-4]
                    if not machines:
                        raise ValueError(f"工序 {j} 未分配到任何机器（phi 变量异常）")
                    result["phi"][j] = machines[0]
                # 处理 mu（机械手分配）
                for j in self.params['J']:
                    robots = [r for r in self.params['R'] if abs(self.mu[j, r].X - 1) < 1e-4]
                    if not robots:
                        raise ValueError(f"工序 {j} 未分配到任何机械手（mu 变量异常）")
                    result["mu"][j] = robots[0]
                # 补充排序变量（可选）
                result["y"] = {(j, jp, k): v.X for (j, jp, k), v in self.y.items() if v.X == 1}
                result["z"] = {(j, jp, r): v.X for (j, jp, r), v in self.z.items() if v.X == 1}
                result["temp_z"] = {(j1, j2, r): v.X for (j1, j2, r), v in self.temp_z.items() if v.X == 1}
                return result
            except Exception as e:
                print(f"生成结果时出错：{e}")
                return {"solve_time": solve_time, "status": self.model.status, "error": str(e)}
        else:
            # 无可行解时的处理
            if self.model.status == GRB.INFEASIBLE:
                print("模型不可行，生成冲突约束文件...")
                self.model.computeIIS()
                self.model.write("infeasible_constraints.ilp")
                print("冲突约束已保存到 infeasible_constraints.ilp")
            print(f"求解状态: {self.model.status}（无可行解）")
            return {"solve_time": solve_time, "status": self.model.status}







def print_schedule_results(result):
    """
    打印模型求解结果
    :param result:
    :return:
    """

    if result is not None and "status" not in result:
        # 打印整体信息
        print("=" * 80)
        print(f"{'半导体生产调度优化结果':^80}")
        print("=" * 80)
        print(f"模型求解时间: {result['solve_time']:.4f} 秒")
        print(f"最小最大完工时间(Cmax): {result['Cmax']:.2f} 秒")
        print("-" * 80 + "\n")

        # 打印工序时间表（按工序顺序）
        print(f"{'工序时间表':^80}")
        print("-" * 80)
        # 表头
        print(f"{'工序':<6} | {'运输任务':^36} | {'加工任务':^36}")
        print(
            f"{'编号':<6} | {'开始时间':<10} {'执行时间':<10} {'机械手':<10} | {'开始时间':<10} {'执行时间':<10} {'机器':<10}")
        print("-" * 80)

        # 按工序顺序输出
        for j in sorted(result['SO'].keys()):
            # 运输任务信息
            st_time = f"{result['ST'][j]:.2f}"
            tt_time = f"{result['Tt'][j]:.2f}"
            transport_robot = result['mu'][j]

            # 加工任务信息
            so_time = f"{result['SO'][j]:.2f}"
            pt_time = f"{result['Pt'][j]:.2f}"
            process_machine = result['phi'][j]

            # 格式化一行
            print(
                f"{j:<6} | {st_time:<10} {tt_time:<10} {transport_robot:<10} | {so_time:<10} {pt_time:<10} {process_machine:<10}")

        print("\n" + "-" * 80)

        # 打印资源分配汇总
        print(f"{'资源分配汇总':^80}")
        print("-" * 80)
        print("机器分配情况:")
        for machine in sorted(set(result['phi'].values())):
            processes = [j for j, m in result['phi'].items() if m == machine]
            print(f"  机器 {machine}: 负责工序 {processes}")

        print("\n机械手分配情况:")
        for robot in sorted(set(result['mu'].values())):
            transports = [j for j, r in result['mu'].items() if r == robot]
            print(f"  机械手 {robot}: 负责工序 {transports}")

        print("\n" + "=" * 80)
    else:
        print("模型求解失败或未找到最优解")
        if "status" in result:
            print(f"求解状态代码: {result['status']}")


def print_schedule_results2(result, machine_map, TM_map):
    """
    打印模型求解结果，即使未找到最优解也会尝试打印可行解，有可行解时绘制甘特图
    :param result: 求解结果字典，可能包含状态码、可行解等信息
    :param machine_map: 机器相关映射信息，用于绘制甘特图
    :param TM_map: 运输相关映射信息，用于绘制甘特图
    :return:
    """
    # 1. 检查结果是否有效（放在最前面，避免后续变量引用错误）
    if result is None:
        print("无求解结果数据")
        return

    # 2. 提取关键信息（状态码、可行解标识）
    status = result.get("status", None)
    # 判断是否有完整的可行解数据
    required_keys = ['SO', 'ST', 'Tt', 'Pt', 'mu', 'phi']
    has_feasible_solution = all(key in result for key in required_keys)

    # 3. 打印整体信息（标题和状态）
    print("=" * 80)
    if status is None:
        print(f"{'半导体生产调度优化结果（最优解）':^80}")
    else:
        # 状态码说明（扩展常见状态，更清晰）
        status_info = {
            2: "已找到最优解",
            3: "已找到可行解（非最优）",
            9: "达到时间限制，已找到可行解",
            10: "模型不可行（无可行解）",
            11: "模型无界（目标可无限优化）",
            12: "求解被用户中断"
        }.get(status, f"未知状态码: {status}")
        print(f"{'半导体生产调度优化结果（' + status_info + '）':^80}")
    print("=" * 80)

    # 4. 打印基础指标（求解时间、最大完工时间）
    if 'solve_time' in result:
        print(f"模型求解时间: {result['solve_time']:.4f} 秒")
    if 'Cmax' in result:
        print(f"最大完工时间(Cmax): {result['Cmax']:.2f} 秒")
    print("-" * 80 + "\n")

    # 5. 打印工序时间表和资源分配（仅当有可行解时）
    if has_feasible_solution:
        # 打印工序时间表
        print(f"{'工序时间表':^80}")
        print("-" * 80)
        # 表头
        print(f"{'工序':<6} | {'运输任务':^36} | {'加工任务':^36}")
        print(
            f"{'编号':<6} | {'开始时间':<10} {'执行时间':<10} {'机械手':<10} | {'开始时间':<10} {'执行时间':<10} {'机器':<10}")
        print("-" * 80)

        # 按工序编号排序输出
        for j in sorted(result['SO'].keys()):
            # 运输任务信息
            st_time = f"{result['ST'][j]:.2f}"
            tt_time = f"{result['Tt'][j]:.2f}"
            transport_robot = result['mu'][j]

            # 加工任务信息
            so_time = f"{result['SO'][j]:.2f}"
            pt_time = f"{result['Pt'][j]:.2f}"
            process_machine = result['phi'][j]

            # 格式化输出一行
            print(
                f"{j:<6} | {st_time:<10} {tt_time:<10} {transport_robot:<10} | {so_time:<10} {pt_time:<10} {process_machine:<10}")

        # 打印资源分配汇总
        print("\n" + "-" * 80)
        print(f"{'资源分配汇总':^80}")
        print("-" * 80)

        # 机器分配情况
        print("机器分配情况:")
        machines = sorted(set(result['phi'].values()))
        for machine in machines:
            processes = [j for j, m in result['phi'].items() if m == machine]
            print(f"  机器 {machine}: 负责工序 {processes}")

        # 机械手分配情况
        print("\n机械手分配情况:")
        robots = sorted(set(result['mu'].values()))
        for robot in robots:
            transports = [j for j, r in result['mu'].items() if r == robot]
            print(f"  机械手 {robot}: 负责工序 {transports}")

        print("\n" + "=" * 80)

        # 绘制甘特图逻辑，有可行解时调用绘图函数
        print("===============开始绘制甘特图========================")
        try:
            fig = plot_combined_gantt(result, 7, (16, 10), machine_map, TM_map)
            plt.show()  # 显示甘特图图表
        except Exception as e:
            print(f"绘制甘特图时发生错误: {e}")
    else:
        # 无可行解时的提示（区分“解析错误”和“真无可行解”）
        if "error" in result:
            print(f"找到可行解，但解析失败：{result['error']}")
        else:
            print("未找到任何可行解，无法展示工序和资源分配详情，也无法绘制甘特图")
        if status is not None:
            print(f"状态码 {status} 说明: {status_info}")  # 复用之前定义的状态说明
        print("\n" + "=" * 80)


def plot_combined_gantt(
        result: Dict,
        ops_per_job: int = 7,
        figsize: tuple = (16, 10),
        machine_map: Optional[Dict] = None,
        robot_map: Optional[Dict] = None
):
    """
    绘制包含机器加工任务和机械手运输任务的甘特图，每个工件的工序用同一颜色表示

    参数:
        result: 模型求解结果字典，需包含:
            - 'SO'（加工开始时间）、'Pt'（加工时间）、'phi'（机器分配）
            - 'ST'（运输开始时间）、'Tt'（运输时间）、'mu'（机械手分配）
        ops_per_job: 每个工件的工序数量
        figsize: 图表尺寸
        machine_map: 机器编号映射字典，如{1: 'M1', 2: 'M2', ...}
        robot_map: 机械手编号映射字典，如{1: 'TM1', 2: 'TM2', ...}
    """
    # 忽略mpld3的混合变换警告（如果仍需使用mpld3）
    import warnings
    warnings.filterwarnings("ignore", message="Blended transforms not yet supported.")

    import matplotlib.pyplot as plt
    from matplotlib.patches import Patch
    import matplotlib as mpl

    # 设置中文字体，避免使用可能引发混合变换的复杂配置
    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
    plt.rcParams["axes.unicode_minus"] = False

    # 默认映射关系
    if machine_map is None:
        machine_map = {i: f'M{i}' for i in range(1, 16)}  # 机器1-15映射为M1-M15
    if robot_map is None:
        robot_map = {1: 'TM1', 2: 'TM2', 3: 'TM3'}  # 机械手1-3映射为TM1-TM3

    # 1. 提取工序信息
    process_ops = []  # (工件号, 工序号, 开始时间, 加工时间, 机器)
    transport_ops = []  # (工件号, 工序号, 开始时间, 运输时间, 机械手)

    for j in result['SO'].keys():
        job_id = (j - 1) // ops_per_job + 1  # 计算工序j所属的工件号（1-based）

        # 加工任务信息
        process_start = result['SO'][j]
        process_duration = result['Pt'][j]
        machine = result['phi'][j]
        process_ops.append((job_id, j, process_start, process_duration, machine))

        # 运输任务信息
        transport_start = result['ST'][j]
        transport_duration = result['Tt'][j]
        robot = result['mu'][j]
        transport_ops.append((job_id, j, transport_start, transport_duration, robot))

    # 2. 准备资源信息（机器和机械手）
    machines = sorted(set(machine for _, _, _, _, machine in process_ops))
    robots = sorted(set(robot for _, _, _, _, robot in transport_ops))

    # 所有资源（机器 + 机械手）及它们的位置映射
    all_resources = [machine_map[m] for m in machines] + [robot_map[r] for r in robots]
    resource_pos = {resource: idx for idx, resource in enumerate(all_resources)}
    num_resources = len(all_resources)

    # 3. 颜色映射（每个工件分配唯一颜色）
    unique_jobs = sorted(set(job_id for job_id, _, _, _, _ in process_ops))
    num_jobs = len(unique_jobs)
    cmap = plt.cm.get_cmap('tab20', num_jobs)
    job_color = {job_id: cmap(i) for i, job_id in enumerate(unique_jobs)}

    # 4. 绘制甘特图
    fig, ax = plt.subplots(figsize=figsize)

    # 绘制加工任务
    for job_id, j, start, duration, machine in process_ops:
        resource_name = machine_map[machine]
        y_pos = resource_pos[resource_name]

        ax.barh(
            y=y_pos,
            width=duration,
            left=start,
            height=0.35,  # 加工任务条高度
            color=job_color[job_id],
            edgecolor='black',
            linewidth=0.7,
            alpha=0.9
        )
        # 标注工序编号（使用数据坐标，避免混合变换）
        ax.text(
            x=start + duration / 2,
            y=y_pos,
            s=f"j{j}",
            ha='center',
            va='center',
            fontsize=7,
            color='black',
            fontweight='bold',
            transform=ax.transData  # 明确使用数据坐标变换
        )

    # 绘制运输任务
    for job_id, j, start, duration, robot in transport_ops:
        resource_name = robot_map[robot]
        y_pos = resource_pos[resource_name]

        ax.barh(
            y=y_pos,
            width=duration,
            left=start,
            height=0.35,  # 运输任务条高度
            color=job_color[job_id],
            edgecolor='black',
            linewidth=0.7,
            alpha=0.7,
            hatch='//'  # 斜线图案区分运输任务
        )

    # 5. 图表美化（避免使用可能引发混合变换的相对坐标）
    # 纵轴设置
    ax.set_yticks(range(num_resources))
    ax.set_yticklabels(all_resources, fontsize=10)
    ax.set_ylabel('资源', fontsize=12)

    # 横轴设置
    all_start_times = [p[2] for p in process_ops] + [t[2] for t in transport_ops]
    all_durations = [p[3] for p in process_ops] + [t[3] for t in transport_ops]
    max_end_time = max(s + d for s, d in zip(all_start_times, all_durations)) if all_start_times else 0
    ax.set_xlim(0, max_end_time * 1.05)  # 留5%余量
    ax.set_xlabel('时间（秒）', fontsize=12)

    # 标题
    ax.set_title(f'半导体生产调度甘特图（{num_jobs}个工件）', fontsize=14, pad=10)

    # 添加网格线
    ax.grid(axis='x', linestyle='--', alpha=0.7)

    # 添加资源类型分隔线（使用数据坐标）
    machine_count = len(machines)
    if machine_count > 0 and num_resources > machine_count:
        ax.axhline(y=machine_count - 0.5, color='gray', linestyle='-', linewidth=1.5)

        # 使用图例替代旋转文本标注，避免混合变换
        ax.text(0.01, 0.5, '加工机器', transform=ax.transAxes,
                ha='left', va='center', rotation=90, fontsize=11, fontweight='bold')
        ax.text(0.01, (machine_count + num_resources) / (2 * num_resources),
                '运输机械手', transform=ax.transAxes,
                ha='left', va='center', rotation=90, fontsize=11, fontweight='bold')

    # 优化图例（避免过多项导致显示问题）
    legend_items = []
    # 限制显示的工件图例数量，过多时用省略号
    if num_jobs <= 10:
        legend_items = [Patch(color=job_color[job_id], label=f'工件 {job_id}') for job_id in unique_jobs]
    else:
        legend_items = [Patch(color=job_color[job_id], label=f'工件 {job_id}') for job_id in unique_jobs[:5]]
        legend_items.append(Patch(color='white', label='...'))
        legend_items.extend([Patch(color=job_color[job_id], label=f'工件 {job_id}') for job_id in unique_jobs[-5:]])

    # 添加加工和运输的图例
    legend_items.append(Patch(facecolor='white', edgecolor='black', label='加工任务'))
    legend_items.append(Patch(facecolor='white', edgecolor='black', hatch='//', label='运输任务'))

    # 调整图例位置，避免超出边界
    ax.legend(handles=legend_items, title='图例',
              bbox_to_anchor=(1.02, 1), loc='upper left', fontsize=10,
              borderaxespad=0)

    # 调整布局
    plt.tight_layout()

    return fig  # 返回图表对象

class ParamterGenerator:
    """生成模型相关参数"""

    def __init__(self, job_num: int, ops_num: int,  K: List[int], R: List[int]):
        self.job_nums = job_num  # 工件数量
        self.ops_nums = ops_num  # 每个工件的工序数量
        self.total_ops_num = job_num * ops_num  # 所有工件的总工序数

        self.J = list(range(1,self.total_ops_num+1))  # 工序集合：所有工序编号
        self.K = K  # 机器集合（[1,2,...,15]）
        self.R = R  # 机械手集合（[1,2,3]）

        self.feasible_rules: Dict[int, List[int]] = self.define_feasible_rules()  # 加工可行
        self.process_time_rules = self.define_process_time_rules()
        self.transport_rules = self.define_transport_rules()

    def define_feasible_rules(self):
        """ B类型工件的7个工序：各个工序的可行加工机器 """

        return {
            1:[15],
            2:[11,12],
            3:[7,8],
            4:[13],
            5:[1,2],
            6:[14],
            7:[11,12]
        }

    def define_process_time_rules(self):
        """ B类型工件的各个工序在可行机器上的加工时间 """
        # 可以加上 开关阀门时间

        return {
            1:[8],
            2:[15],
            3:[70],
            4:[0],
            5:[300],
            6:[70],
            7:[20]
        }

    def define_transport_rules(self):
        """ B类型工件的各个工序运输任务的可用机械手 """

        return {
            1:[1],
            2:[1],
            3:[2],
            4:[2],
            5:[3],
            6:[3],
            7:[2]
        }

    def get_base_step(self,ops_id: int):
        """
        工序映射：
        例如：总工序编号为14的工序，对于哪一个具体工序（1-7）
        """
        return ((ops_id-1) % self.ops_nums) + 1

    def generate_PM_PP(self):
        """
        生成加工可行性字典 PM
        加工时间字典 PP

        """

        PM = {}
        PP = {}
        for j in self.J:
            # 工序序号映射到 1-7
            base_step = self.get_base_step(j)
            # 检查j是否为整数
            if not isinstance(j, int):
                raise TypeError(f"工序j必须是整数，当前为{type(j)}，值为{j}")
            for k in self.K:
                if base_step in self.feasible_rules and k in self.feasible_rules[base_step]:
                    PM[(j,k)] = 1
                    PP[(j,k)] = self.process_time_rules[base_step][0]
                else:
                    PM[(j,k)] = 0
                    PP[(j,k)] = 1e10
        return PM,PP

    def generate_TM(self):
        """ 生成运输可行性字典 """

        TM = {}
        for j in self.J:
            # 工序序号映射到 1-7
            base_step = self.get_base_step(j)
            for r in self.R:
                if base_step in self.transport_rules and r in self.transport_rules[base_step]:
                    TM[(j,r)] = 1
                else:
                    TM[(j,r)] = 0
        return TM

    def generate_TT(self):
        """
        生成运输时间：任意两台机器之间的运输时间均为2秒 ：
        """

        TT = {}
        for k_prev in self.K:
            for k_curr in self.K:
                if k_prev == k_curr:
                    TT[(k_prev, k_curr)] = 0
                else:
                    TT[(k_prev, k_curr)] = 2
        return TT

    def generate_Ns(self):
        """
        生成相同路径、同一工艺步骤的晶圆工序集合
            Ns格式：[[工艺步骤1的所有工序], [工艺步骤2的所有工序], ...]
            例如：[[1,8,15...], [2,9,16...], ...]
        :return: List[List[int]]，按工艺步骤分组的工序集合
        """

        Ns: List[List[int]] = []

        # 遍历每个工艺步骤
        for step in range(1, self.ops_nums+1):
            # 相同工艺步骤集合：收集所有工件的第step道工序
            step_operations = []
            for workpiece in range(1, self.job_nums + 1):
                # 计算该工件的第step道工序编号
                op_number = (workpiece - 1) * self.ops_nums + step
                step_operations.append(op_number)
            # 将该工艺步骤的所有工序加入Ns
            Ns.append(step_operations)
        return Ns

    def generate_Nj(self):
        """
            生成同一个工件的工序集合Nj，每个子列表包含某一工件的所有工序。
            例如：工件1的工序为[1-7]，工件2的工序为[8-14]，以此类推。
        """
        Nj: List[List[int]] = []

        for job_id in range(1, self.job_nums + 1):
            # 计算当前工件的首个工序和最后一个工序编号
            start_op = (job_id - 1) * self.ops_nums + 1
            end_op = job_id * self.ops_nums
            # 生成当前工件的工序列表
            job_ops = list(range(start_op, end_op + 1))
            Nj.append(job_ops)

        return Nj





    def generate_all_params(self):
        """ 生成模型中需要的所有参数 """
        pm_dict, pp_dict = self.generate_PM_PP()

        # 每个工件的首道工序:[1,8,15...]
        Nf = [i * self.ops_nums + 1 for i in range(self.job_nums)]

        # 每个工件的最后一道工序（第7,14,21,...道工序）
        Ne = [(i + 1) * self.ops_nums for i in range(self.job_nums)]

        # 相同路径的晶圆的工序组（用于禁止超片约束）
        # 由于 任务b中所有工件的工艺路径相同
        Ns = self.generate_Ns()

        Nj = self.generate_Nj()

        return {
            'J': self.J,
            'K': self.K,
            'R': self.R,
            'Nf': Nf,
            'Ns': Ns,
            'Ne': Ne,
            'Nj': Nj,
            'M': 1e6,
            'alpha': 15,
            'beta': 30,
            'PM': pm_dict,
            'PP': pp_dict,
            'TM': self.generate_TM(),
            'TT': self.generate_TT()
        }





if __name__ == "__main__":
    # 参数初始化
    job_nums = 75  # 工件数量
    ops_nums = 7  # 每个工件的工序数
    K = list(range(1, 16)) # PM1-10, LLA/LLB  LLC/LLD  AL 1-15
    R =  list(range(1, 4))  # 机械手TM1 , TM2  TM3

    machine_map = {1:"PM1",2:"PM2",3:"PM3",4:"PM4",5:"PM5",6:"PM6",7:"PM7",8:"PM8",9:"PM9",10:"PM10",
                   11:"LLA",12:"LLB",13:"LLC",14:"LLD",
                   15:"AL"}

    TM_map = {1:"TM1",2:"TM2",3:"TM3"};


    # 生成参数
    param_generator = ParamterGenerator(job_nums, ops_nums, K, R)
    params = param_generator.generate_all_params()

    print("============模型参数============")
    print(params)
    # print(params['PM'])
    # print("--------------------------")
    # print(params['PP'])
    # print("--------------------------")
    #
    # print(params['TM'])
    # print("--------------------------")
    #
    # print(params['TT'])

    # # 创建并求解模型
    model = SemiconductorScheduler(params)
    result = model.optimize()
    # 5. 输出求解结果
    print_schedule_results2(result,machine_map, TM_map)



